#	Formatter.py
#
#	------------------------------------------------------------
#	Copyright 2002, 2004 by Samuel Reynolds. All rights reserved.
#
#	Permission to use, copy, modify, and distribute this software and its
#	documentation for any purpose and without fee is hereby granted,
#	provided that the above copyright notice appear in all copies and that
#	both that copyright notice and this permission notice appear in
#	supporting documentation, and that the name of Samuel Reynolds
#	not be used in advertising or publicity pertaining to distribution
#	of the software without specific, written prior permission.
#
#	SAMUEL REYNOLDS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
#	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
#	EVENT SHALL SAMUEL REYNOLDS BE LIABLE FOR ANY SPECIAL, INDIRECT, OR
#	CONSEQUENTIAL DAMAGES, OR FOR ANY DAMAGES WHATSOEVER RESULTING FROM
#	LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
#	NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
#	WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#	------------------------------------------------------------


"""
Formatters for converting and validating data values.
"""

import copy, re, time

class Formatter(object):
	"""
	Formatter/validator for data values.
	"""
	def __init__(self, required=False, *args, **kwargs):
		self.required = required
	
	def is_empty(self, value):
		return (value is None or value == '')
	
	# Default (dummy) validate routine
	def validate(self, value):
		"""
		Return true if value is valid for the field.
		value is a string from the UI.
		"""
		return not (self.required and self.is_empty(value))
	
	# Default (dummy) format routine
	def format(self, value):
		"""Format a value for presentation in the UI."""
		if value == None:
			return ''
		return str(value)
	
	# Default (dummy) coerce routine
	def coerce(self, value):
		"""Convert a string from the UI into a storable value."""
		return value

class FormatterMeta(type):
	"""
	Metaclass for subclasses of Formatter.
	
	Each instance class MUST define either validate(self, value) method
	or re_validation regular expression string.
	
	If the latter, the validate method will be autogenerated.
	
	If re_validation is defined, validate method is overridden to
	validate the string value against the regular expression.
	
	If re_validation_flags is defined, the flags will be
	used when the re_validation regular expression string is compiled.
	
	Each instance class MAY define:
	-	format(self, value) method.
	-	coerce(self, value) method.
	"""
	def __new__(cls, classname, bases, classdict):
		newdict = copy.copy(classdict)
		
		# Generate __init__ method
		# Direct descendants of Formatter automatically get __init__.
		# Indirect descendants don't automatically get one.
		if Formatter in bases:
			def __init__(self, *args, **kwargs):
				Formatter.__init__(self, *args, **kwargs)
				initialize = getattr(self, 'initialize', None)
				if initialize:
					initialize()
			newdict['__init__'] = __init__
		else:
			def __init__( self, *args, **kwargs ):
				super(self.__class__,self).__init__(*args, **kwargs)
				initialize = getattr(self, 'initialize', None)
				if initialize:
					initialize()
			newdict['__init__'] = __init__
		
		# Generate validate-by-RE method if specified
		re_validation = newdict.get('re_validation', None)
		if re_validation:
			# Override validate method
			re_validation_flags = newdict.get('re_validation_flags', 0)
			newdict['_re_validation'] = re.compile(re_validation, re_validation_flags)
			
			def validate(self, value):
				return not (self.required and self.is_empty(value)) and ((not self.required and value is None) or (self._re_validation.match(value) != None))
			newdict['validate'] = validate
		
		# Delegate class creation to the expert
		return type.__new__(cls, classname, bases, newdict)


class IdFormatter(Formatter):
	"""
	ID is assumed to be a large (32 bit?) unsigned integer.
	"""
	__metaclass__ = FormatterMeta
	re_validation = '^[0-9]+$'
	
	def coerce(self, value):
		if value: return long(value)
		return value

class StringFormatter(Formatter):
	__metaclass__ = FormatterMeta

class AlphaFormatter(StringFormatter):
	"""Alphabetic characters only."""
	re_validation = '^[a-zA-Z]*$'

class AlphaNumericFormatter(StringFormatter):
	"""Alphanumeric characters only."""
	re_validation = '^[a-zA-Z0-9]*$'

class TextFormatter(StringFormatter):
	"""Alphanumeric characters only."""
	re_validation = '^[a-zA-Z0-9 \\.\\,\\-\\\']*$'

class EmailFormatter(StringFormatter):
	"""Internet email addresses (more or less)."""
	# This regex does not match all legal email addresses, but
	# it does a pretty good job.
	# Strangely enough, '/' is legal in email addresses.
	# However, I've never seen it used, so I prefer to leave it out.
	_re_subs = {
			'sub1' : r'[a-zA-Z~_-][a-zA-Z0-9_:~-]*',
			'sub2' : r'(\.[a-zA-Z0-9_:~-]+)*',
			'sfx'  : r'\.[a-zA-Z]{2,3}'
		}
	re_validation = '^%(sub1)s%(sub2)s[@]%(sub1)s%(sub2)s%(sfx)s$' % _re_subs

class MoneyFormatter(StringFormatter):
	"""Assumes decimal money, but doesn't assume currency."""
	re_validation = '^(([0-9]+([.][0-9]{2})?)|([0-9]*[.][0-9]{2}))$'

class IntFormatter(Formatter):
	"""Signed or unsigned integer."""
	__metaclass__ = FormatterMeta
	#re_validation = '^[-+]?[0-9]+$'
	
	def validate(self, value):
		try:
			v = int(value)
			return True
		except:
			return False

	def coerce(self, value):
		return int(value)

class UIntFormatter(Formatter):
	"""Unsigned integer."""
	__metaclass__ = FormatterMeta
	#re_validation = '^[0-9]+$'
	
	def validate(self, value):
		try:
			v = int(value)
			return v >= 0
		except:
			return False
	
	def coerce( self, value ):
		return int(value)

class FloatFormatter(Formatter):
	"""Signed or unsigned floating-point number."""
	__metaclass__ = FormatterMeta
	#re_validation = '^[-+]?(([0-9]+[.]?[0-9]*)|([0-9]*[.]?[0-9]+))$'
	
	def validate(self, value):
		try:
			float(value)
			return True
		except:
			return False
	
	def coerce(self, value):
		return float(value)

class DoubleFormatter( FloatFormatter ):
	pass

class UFloatFormatter( Formatter ):
	"""Unsigned floating-point number."""
	__metaclass__ = FormatterMeta
	#re_validation = '^(([0-9]+[.]?[0-9]*)|([0-9]*[.]?[0-9]+))$'
	
	def validate(self, value):
		try:
			v = float(value)
			return v >= 0
		except:
			return False
	
	def coerce(self, value):
		return float(value)

class UDoubleFormatter( UFloatFormatter ):
	pass

class TimeElapsedFormatter( Formatter ):
	"""Elapsed time string (HH:MM:SS)."""
	__metaclass__ = FormatterMeta
	re_validation = '^([1][0-2]|[0]?[0-9]):[0-5][0-9](:[0-5][0-9])?$'

class DateFormatter( Formatter ):
	"""
	Date string (YYYY-MM-DD).
	
	Storage format:      YYYY-MM-DD
	Presentation format: YYYY-MM-DD
	
	Accepts only YYYY-MM-DD format (allows variant separators '/' and '.').
	Accepts dates in range (1000-2999)-(01-12)-(01-31).
	Leading zeros optional in month and day.
	Does not enforce # of days in month.
	"""
	__metaclass__ = FormatterMeta
	
	re_validation = r'^[1-2][0-9]{3}([-/.])([0][1-9]|[1][0-2])\1([0][1-9]|[12][0-9]|[3][0-1])$'
	
	def coerce( self, value ):
		"""Convert alternate date separators to '-'."""
		return re.sub( r'[/.]', '-', value )

class DateFormatterMDY( DateFormatter ):
	"""Alternate date string (MM-DD-YYYY).
	
	Storage format:      YYYY-MM-DD
	Presentation format: MM-DD-YYYY
	
	Accepts only MM-DD-YYYY format  (allows variant separators '/' and '.').
	Accepts dates in range (01-12)-(01-31)-(1000-2999).
	Leading zeros optional in month and day.
	Does not enforce # of days in month.
	"""
	re_validation = r'^([0][1-9]|[1][0-2])([-/.])([0][1-9]|[12][0-9]|[3][0-1])\1[12][0-9]{3}$'

	def format( self, value ):
		dt = time.strptime( value, '%Y-%m-%d' )
		return time.strftime( '%m-%d-%Y', dt )
	
	def coerce( self, value ):
#		value = re.sub( r'[/.]', '-', value )
#		dt = time.strptime( value, '%m-%d-%Y' )
#		return time.strftime( '%Y-%m-%d', dt )
		m, d, y = re.split( '[-/.]', value )
		return '%04d-%02d-%02d' % ( int(y), int(m), int(d) )

class TimeFormatter( Formatter ):
	"""
	Time string (12-hour or 24-hour format, with or without seconds or am/pm).
	
	Storage format:      HH:MM:SS -- 24-hour format.
	Presentation format: HH:MM    -- 24-hour format.
	
	Accepts 12-hour or 24-hour format, with or without seconds or am/pm.
	"""
	__metaclass__ = FormatterMeta
	
	reTime24 = r'(([0]?[0-9]|[1][0-9]|[2][0-3]):[0-5][0-9](:[0-5][0-9])?)'
	reTimeAP = r'(([1][0-2]|[0]?[0-9]):[0-5][0-9](:[0-5][0-9])?[ ]*([aApP][mM])?)'
	re_validation = r'^%s|%s$' % ( reTime24, reTimeAP )
	
	def format( self, value ):
		return ':'.join( value.split(':')[:2] )
	
	def coerce( self, value ):
		for fmt in ( '%H:%M:%S', '%H:%M', '%I:%M:%S %p', '%I:%M %p','%I:%M:%S' ):
			try:
				dt = time.strptime( value, fmt )
				break
			except ValueError:
				pass
		return time.strftime( '%H:%M:%S', dt )

class TimeFormatter12H( TimeFormatter ):
	"""
	Alternate time string (12-hour format, without seconds).
	
	Storage format:      HH:MM(:SS) -- 24-hour format.
	Presentation format: HH:MM( aa) -- 12-hour format.
	                     (aa may be 'am' or 'pm')
	"""
	
	def format( self, value ):
		dt = time.strptime( value, '%H:%M:%S' )
		return time.strftime( '%I:%M %p', dt )

class DateTimeFormatter( Formatter ):
	"""
	Date/time string.
	
	Uses a DateFormatter and a TimeFormatter.
	"""
	__metaclass__ = FormatterMeta
	
	# Storage format: YYYY-MM-DD HH:MM:SS -- 24-hour format.
	# Presentation format: same as storage format.
	
	def validate( self , value ):
		datef = DateFormatter()
		timef = TimeFormatter()
		date, time = re.split( r'[ ]+', value )
		return ( datef.validate( date ) and timef.validate( time ) )
